﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Netbits.Common.Conditions;

namespace Netbits.Common.General
{
	/// <summary>
	/// Class with simple en/decrypting string/byte[] with password
	/// </summary>
	public static class Crypto
	{
		#region Default Settings

		static readonly string PasswordHash = "k)23iujhf23q0";
		static readonly string SaltKey = "S@LT&KEY";
		static readonly string VIKey = "@1B2c3D4e5F6g7H8";

		#endregion

		#region Public Functions

		/// <summary>
		/// Encrypt specified byte[] data with specified password
		/// </summary>
		/// <param name="Data_">Data to encrypt</param>
		/// <param name="Password_">Encryption password</param>
		/// <returns>Encrypted data otherwise any exception</returns>
		public static byte[] Encrypt( byte[] Data_, string Password_ )
		{
			Condition.Requires( Data_, "Data_" ).IsNotNull();
			Condition.Requires( Password_, "Password_" ).IsNotNull().IsNotEmpty();

			byte[] keyBytes = new Rfc2898DeriveBytes( Password_, Encoding.ASCII.GetBytes( SaltKey ) ).GetBytes( 256 / 8 );
			RijndaelManaged symmetricKey = new RijndaelManaged()
			{
				Mode = CipherMode.CBC,
				Padding = PaddingMode.Zeros
			};
			ICryptoTransform encryptor = symmetricKey.CreateEncryptor( keyBytes, Encoding.ASCII.GetBytes( VIKey ) );

			byte[] cipherTextBytes;
			MemoryStream memoryStream = new MemoryStream();
	
			CryptoStream cryptoStream = new CryptoStream( memoryStream, encryptor, CryptoStreamMode.Write );
			cryptoStream.Write( Data_, 0, Data_.Length );
			cryptoStream.FlushFinalBlock();

			cipherTextBytes = memoryStream.ToArray();
			memoryStream.Dispose();

			return cipherTextBytes;
		}

		/// <summary>
		/// Encrypt the specified text with default password hash
		/// </summary>
		/// <param name="Text_">Text to encrypt</param>
		/// <returns>Encrypted text otherwise any exception</returns>
		public static byte[] Encrypt( string Text_ )
		{
			Condition.Requires( Text_, "Text_" ).IsNotNull().IsNotEmpty();

			byte[] plainTextBytes = Encoding.UTF8.GetBytes( Text_ );
			return Encrypt( plainTextBytes, PasswordHash );
		}

		/// <summary>
		/// Decrypt from specified Stream with default password hash
		/// </summary>
		/// <param name="Stream_">Stream to decrypt</param>
		/// <returns>Decrypted stream otherwise any exception</returns>
		public static byte[] Decrypt( Stream Stream_ )
		{
			return Decrypt( Stream_, PasswordHash );
		}

		/// <summary>
		/// Decrypt from specified stream with specified password hash
		/// </summary>
		/// <param name="Stream_">Stream to decrypt</param>
		/// <param name="Password_">password hash for decryption</param>
		/// <returns>Decrypted stream otherwise any exception</returns>
		public static byte[] Decrypt( Stream Stream_, string Password_ )
		{
			Condition.Requires( Stream_, "Stream_" ).IsNotNull();
			Condition.Requires( Password_, "Password_" ).IsNotNull().IsNotEmpty();

			byte[] keyBytes = new Rfc2898DeriveBytes( Password_, Encoding.ASCII.GetBytes( SaltKey ) ).GetBytes( 256 / 8 );
			var symmetricKey = new RijndaelManaged()
			{
				Mode = CipherMode.CBC,
				Padding = PaddingMode.None
			};

			var decryptor = symmetricKey.CreateDecryptor( keyBytes, Encoding.ASCII.GetBytes( VIKey ) );
			var cryptoStream = new CryptoStream( Stream_, decryptor, CryptoStreamMode.Read );
			byte[] plainTextBytes = new byte[Stream_.Length];

			int decryptedByteCount = cryptoStream.Read( plainTextBytes, 0, plainTextBytes.Length );
			cryptoStream.Close();

			return plainTextBytes;
		}

		/// <summary>
		/// Decrypt from specified stream with specified string-encoding settings and default password hash
		/// </summary>
		/// <param name="Stream_">Stream to decrypt</param>
		/// <param name="Encoder_">Encoder for string decoding</param>
		/// <returns>Decrypted string otherwise any exception</returns>
		public static string Decrypt( Stream Stream_, Encoding Encoder_ )
		{
			Condition.Requires( Stream_, "Stream_" ).IsNotNull();
			Condition.Requires( Encoder_, "Encoder_" ).IsNotNull();

			byte[] keyBytes = new Rfc2898DeriveBytes( PasswordHash, Encoding.ASCII.GetBytes( SaltKey ) ).GetBytes( 256 / 8 );
			var symmetricKey = new RijndaelManaged()
			{
				Mode = CipherMode.CBC,
				Padding = PaddingMode.None
			};

			var decryptor = symmetricKey.CreateDecryptor( keyBytes, Encoding.ASCII.GetBytes( VIKey ) );
			var cryptoStream = new CryptoStream( Stream_, decryptor, CryptoStreamMode.Read );
			byte[] plainTextBytes = new byte[Stream_.Length];

			int decryptedByteCount = cryptoStream.Read( plainTextBytes, 0, plainTextBytes.Length );
			cryptoStream.Close();
			return Encoder_.GetString( plainTextBytes, 0, decryptedByteCount ).TrimEnd( "\0".ToCharArray() );
		}

		/// <summary>
		/// Decrypt specified data with default password has and UTF8 Encoding
		/// </summary>
		/// <param name="CryptedData_">Decrypting data</param>
		/// <returns>Decrypted string otherwise any exception</returns>
		public static string Decrypt( byte[] CryptedData_ )
		{
			return Decrypt( CryptedData_, PasswordHash, Encoding.UTF8 );
		}

		/// <summary>
		/// Decrypt specified data with specified password and string-decoder
		/// </summary>
		/// <param name="CryptedData_">To decrypting data</param>
		/// <param name="Password_">Password hash to use for decrypt</param>
		/// <param name="Encoder_">Encoder that use for string encoding</param>
		/// <returns>Decrypted string otherwise any exception</returns>
		public static string Decrypt( byte[] CryptedData_, string Password_, Encoding Encoder_ )
		{
			Condition.Requires( Encoder_, "Encoder_" ).IsNotNull();

			byte[] buffer = Decrypt( CryptedData_, Password_ );
			return Encoder_.GetString( buffer, 0, buffer.Length ).TrimEnd( "\0".ToCharArray() );
		}

		/// <summary>
		/// Decrypt specified data with specified password
		/// </summary>
		/// <param name="CryptedData_">To decrypting data</param>
		/// <param name="Password_">Password has to use for decrypt</param>
		/// <returns>Decrypted data otherwise any exception</returns>
		public static byte[] Decrypt( byte[] CryptedData_, string Password_ )
		{
			if( CryptedData_ == null )
				throw new ArgumentNullException( "CryptedData_" );

			byte[] keyBytes = new Rfc2898DeriveBytes( Password_, Encoding.ASCII.GetBytes( SaltKey ) ).GetBytes( 256 / 8 );
			var symmetricKey = new RijndaelManaged()
			{
				Mode = CipherMode.CBC,
				Padding = PaddingMode.None
			};

			var decryptor = symmetricKey.CreateDecryptor( keyBytes, Encoding.ASCII.GetBytes( VIKey ) );
			var memoryStream = new MemoryStream( CryptedData_ );
			var cryptoStream = new CryptoStream( memoryStream, decryptor, CryptoStreamMode.Read );
			byte[] plainTextBytes = new byte[CryptedData_.Length];

			int decryptedByteCount = cryptoStream.Read( plainTextBytes, 0, plainTextBytes.Length );
			memoryStream.Dispose();
			Array.Resize( ref plainTextBytes, decryptedByteCount );

			return plainTextBytes;
		}

		#endregion
	}
}
